package com.gwtintegration.smartrf.client;

import java.util.HashSet;
import java.util.Set;

import com.google.gwt.core.shared.GWT;
import com.google.web.bindery.requestfactory.shared.BaseProxy;
import com.google.web.bindery.requestfactory.shared.Receiver;
import com.google.web.bindery.requestfactory.shared.RequestContext;
import com.google.web.bindery.requestfactory.shared.ServerFailure;
import com.gwtintegration.smartrf.shared.DataRequestValueProxy;
import com.gwtintegration.smartrf.shared.DataResponseValueProxy;
import com.gwtintegration.smartrf.shared.DataSourceRequestContext;
import com.smartgwt.client.data.DSRequest;
import com.smartgwt.client.data.DSResponse;
import com.smartgwt.client.data.DataSource;
import com.smartgwt.client.data.Record;
import com.smartgwt.client.data.RecordList;
import com.smartgwt.client.rpc.RPCResponse;
import com.smartgwt.client.types.DSDataFormat;
import com.smartgwt.client.types.DSProtocol;
import com.smartgwt.client.widgets.grid.ListGridRecord;

public abstract class RequestFactoryDataSource<T extends BaseProxy, R extends RequestContext & DataSourceRequestContext<T>> extends DataSource {

	private final Converter converter = GWT.create(Converter.class);

	private final Class<?> proxyClass;

	private final RequestFactoryRequestConverter requestConverter;

	private String[] defaultFetchWith;

	private Set<String> completeFetchWith;

	public RequestFactoryDataSource(Class<?> proxyClass) {
		this.proxyClass = proxyClass;
		this.requestConverter = new RequestFactoryRequestConverter(this);
		this.completeFetchWith = new HashSet<String>();

		setDataProtocol(DSProtocol.CLIENTCUSTOM);
		setDataFormat(DSDataFormat.CUSTOM);
	}

	@Override
	protected Object transformRequest(DSRequest request) {

		String requestId = request.getRequestId();

		DSResponse response = new DSResponse();

		response.setOperationType(request.getOperationType());
		response.setAttribute("clientContext", request.getAttributeAsObject("clientContext"));

		switch (request.getOperationType()) {
			case FETCH:
				executeFetch(requestId, request, response);
				break;
			case ADD:
				executeAdd(requestId, request, response);
				break;
			case UPDATE:
				executeUpdate(requestId, request, response);
				break;
			case REMOVE:
				executeRemove(requestId, request, response);
				break;
			case VALIDATE:
				executeValidate(requestId, request, response);
				break;
			case CLIENTEXPORT:
				executeClientExport(requestId, request, response);
				break;
			case CUSTOM:
				executeCustom(requestId, request, response);
				break;
			default:
				break;
		}
		return request.getData();
	}

	protected void executeFetch(final String requestId, final DSRequest request, final DSResponse response) {

		R requestContext = getRequestContext();

		DataRequestValueProxy dataRequest = requestConverter.convert(request, requestContext);
		String[] fetchWith = getFetchWith(request, true, false);

		requestContext.fetch(dataRequest).with(fetchWith).fire(new Receiver<DataResponseValueProxy<T>>() {
			@Override
			public void onSuccess(DataResponseValueProxy<T> dataResult) {
				onExecuteFetchSuccess(requestId, response, dataResult);
			}

			@Override
			public void onFailure(ServerFailure error) {
				onExecuteFetchFailure(requestId, response, error);
			}
		});
	}

	protected void executeAdd(final String requestId, final DSRequest request, final DSResponse response) {
		R requestContext = getRequestContext();

		final ListGridRecord record = new ListGridRecord(request.getData());
		T proxy = converter.toProxy(record, requestContext, proxyClass.getName());

		String[] fetchWith = getFetchWith(request, true);

		requestContext.add(proxy).with(fetchWith).fire(new Receiver<T>() {
			@Override
			public void onSuccess(T proxy) {
				onExecuteAddSuccess(requestId, response, proxy);
			}

			@Override
			public void onFailure(ServerFailure error) {
				onExecuteAddFailure(requestId, response, error);
			}
		});
	}

	protected void executeUpdate(final String requestId, final DSRequest request, final DSResponse response) {
		R requestContext = getRequestContext();

		Record record = null;
		if (request.getOldValues() != null && request.getOldValues().getJsObj() != null) {
			record = request.getOldValues();
			JsUtils.copyMissingValues(request.getData(), record.getJsObj());
		} else {
			record = new ListGridRecord(request.getData());
		}

		T proxy = converter.toProxy(record, requestContext, proxyClass.getName());

		if (proxy == null) {
			throw new IllegalArgumentException("The record proxy is null.");
		}

		String[] fetchWith = getFetchWith(request, true);

		requestContext.update(proxy).with(fetchWith).fire(new Receiver<T>() {
			@Override
			public void onSuccess(T proxy) {
				onExecuteUpdateSuccess(requestId, response, proxy);
			}

			@Override
			public void onFailure(ServerFailure error) {
				onExecuteUpdateFailure(requestId, response, error);
			}
		});
	}

	protected void executeRemove(final String requestId, final DSRequest request, final DSResponse response) {
		R requestContext = getRequestContext();

		Record record = new ListGridRecord(request.getData());
		T proxy = converter.toProxy(record, requestContext, proxyClass.getName());

		requestContext.remove(proxy).fire(new Receiver<Void>() {
			@Override
			public void onSuccess(Void result) {
				onExecuteRemoveSuccess(requestId, response);
			}

			@Override
			public void onFailure(ServerFailure error) {
				onExecuteUpdateFailure(requestId, response, error);
			}
		});
	}

	protected void executeCustom(String requestId, DSRequest request, DSResponse response) {
		throw new UnsupportedOperationException("The custom operation is not supported.");
	}

	protected void executeClientExport(String requestId, DSRequest request, DSResponse response) {
		throw new UnsupportedOperationException("The export operation is not supported.");
	}

	protected void executeValidate(String requestId, DSRequest request, DSResponse response) {
		throw new UnsupportedOperationException("The validate operation is not supported.");
	}

	protected void onExecuteFetchFailure(String requestId, DSResponse response, ServerFailure error) {
		onExecuteOperationFailure(requestId, response, error);
	}

	protected void onExecuteFetchSuccess(final String requestId, final DSResponse response, DataResponseValueProxy<T> dataResult) {

		if (dataResult == null) {
			throw new IllegalArgumentException("The data result of data source" + getID() + " can not be null.");
		}

		RecordList records = new RecordList();

		for (T proxy : dataResult.getContent()) {
			Record record = converter.toRecord(proxy, proxyClass.getName());
			records.add(record);
		}

		response.setData(records.toArray());
		response.setStatus(RPCResponse.STATUS_SUCCESS);
		response.setTotalRows(dataResult.getTotalRows());

		processResponse(requestId, response);
	}

	protected void onExecuteAddFailure(String requestId, DSResponse response, ServerFailure error) {
		onExecuteOperationFailure(requestId, response, error);
	}

	protected void onExecuteAddSuccess(final String requestId, final DSResponse response, T proxy) {
		Record record = converter.toRecord(proxy, proxyClass.getName());
		response.setData(record);
		response.setStatus(RPCResponse.STATUS_SUCCESS);

		processResponse(requestId, response);
	}

	protected void onExecuteUpdateFailure(String requestId, DSResponse response, ServerFailure error) {
		onExecuteOperationFailure(requestId, response, error);
	}

	protected void onExecuteUpdateSuccess(final String requestId, final DSResponse response, T proxy) {
		Record record = converter.toRecord(proxy, proxyClass.getName());
		response.setData(record);
		response.setStatus(RPCResponse.STATUS_SUCCESS);

		processResponse(requestId, response);
	}

	protected void onExecuteRemoveFailure(String requestId, DSResponse response, ServerFailure error) {
		onExecuteOperationFailure(requestId, response, error);
	}

	protected void onExecuteRemoveSuccess(String requestId, DSResponse response) {
		response.setStatus(RPCResponse.STATUS_SUCCESS);
		processResponse(requestId, response);
	}

	protected void onExecuteOperationFailure(String requestId, DSResponse response, ServerFailure error) {
		response.setStatus(RPCResponse.STATUS_FAILURE);
		processResponse(requestId, response);
	}

	protected String[] getFetchWith(DSRequest dsRequest, boolean includeAll) {
		return getFetchWith(dsRequest, false, includeAll);
	}

	protected String[] getFetchWith(DSRequest dsRequest, boolean insideResponse, boolean includeAll) {
		Set<String> fetchWith = new HashSet<String>();

		if (includeAll && !completeFetchWith.isEmpty()) {
			for (String value : completeFetchWith) {
				if (insideResponse) {
					fetchWith.add("content." + value);
				} else {
					fetchWith.add(value);
				}
			}
		}

		RequestFactoryDSRequest rfRequest = new RequestFactoryDSRequest(dsRequest);
		String[] componentFetchWith = rfRequest.getFetchWith();
		if (componentFetchWith != null && componentFetchWith.length > 0) {
			for (String value : componentFetchWith) {
				completeFetchWith.add(value);
				if (insideResponse) {
					fetchWith.add("content." + value);
				} else {
					fetchWith.add(value);
				}
			}
		}

		if (defaultFetchWith != null && defaultFetchWith.length > 0) {
			for (String value : defaultFetchWith) {
				if (insideResponse) {
					fetchWith.add("content." + value);
				} else {
					fetchWith.add(value);
				}
			}
		}

		return fetchWith.toArray(new String[fetchWith.size()]);
	}

	public void setDefaultFetchWith(String... defaultFetchWith) {
		this.defaultFetchWith = defaultFetchWith;
	}

	public String[] getDefaultFetchWith() {
		return defaultFetchWith;
	}

	protected abstract R getRequestContext();
}
